<?php


namespace tmcore\services;

use Firebase\JWT\ExpiredException;
use Firebase\JWT\JWT;
use think\facade\Config;
use tmcore\exceptions\JwtException;
use tmcore\traits\ErrorTrait;

class JwtTokenService
{
    use ErrorTrait;

    // jwt结合redis存储token
    // 支持jwt_token续期
    // 可防止多端token登录

    /**
     * 创建 jwt_token
     * @param int $id
     * @param string $type
     * @param int $exp
     * @param array $params
     * @param bool $is_multipoint
     * @return array
     * @throws JwtException
     */
    public static function createToken(int $id, string $type, int $exp, array $params = [], bool $is_multipoint = true)
    {
        $time = time();
        $host = app('request')->host();
        $params += [
            'iss' => $host, //签发者
            'aud' => $host, //接受者
            'iat' => $time, //签发时间
            'nbf' => $time, //开始能使用的时间
            'exp' => $time + $exp, //过期时间
        ];
        $params['jti'] = ['id' => $id, 'type' => $type, 'exp' => $exp];
        $token = JWT::encode($params, Config::get('core.token_key', 'jwt_token'), 'HS256');

        $cache_key = $type . "_token_" . $id;
        $cache_token = CacheService::get($cache_key);
        $cache_token_arr = $cache_token?:[];
        // 不支持多端登录
        if(!$is_multipoint) $cache_token_arr = [];
        $cache_token_arr[] = [$token, $params['exp'] + 60];
        $res = CacheService::set($cache_key, $cache_token_arr);
        if(!$res) throw new JwtException('缓存失效', 117);
        return ['token' => $token, 'params' => $params];
    }

    /**
     * 验证 jwt_token
     * @param string $token
     * @param bool $is_expire_extension
     * @return array
     * @throws JwtException
     */
    public static function parseToken(string $token, bool $is_expire_extension = false)
    {
        try {
            JWT::$leeway = 60;//当前时间减去60
            $payload = JWT::decode($token, Config::get('core.token_key', 'jwt_token'), ['HS256']);
            if(!$payload) throw new JwtException('token解密失败',10007);

            $token_info = json_decode(json_encode($payload), true, 512, JSON_THROW_ON_ERROR);
            if(empty($token_info['jti'])) throw new JwtException('token解密失败',10007);

            $id = $token_info['jti']['id'];
            $type = $token_info['jti']['type'];
            $exp = $token_info['jti']['exp'];
            //jwt_token 检验
            $res = self::expireTokenCheck($id, $type, $token);
            if(!$res) {
                throw new JwtException('token已过期，请重新登录',10008);
            } else {
                $token_info['exp'] = $res;
            }
            //jwt_token 续期
            if($is_expire_extension) {
                $token_info['exp'] = self::extensionToken($id, $type, $token, $exp);
            }

            return ['id' => $id, 'type' => $type, 'params' => $token_info];
        } catch (ExpiredException $e) {
            $payload = self::decodeToken($token);
            if(empty($payload->jti)) throw new JwtException('token解密失败',10007);

            //jwt过期检验
            if($is_expire_extension) {
                $id = $payload->jti->id;
                $type = $payload->jti->type;
                $exp = $payload->jti->exp;
                //jwt_token 检验
                self::expireTokenCheck($id, $type, $token);
                if(!self::expireTokenCheck($id, $type, $token)) throw new JwtException('token已过期，请重新登录',10008);
                //jwt_token 续期
                $payload['exp'] = self::extensionToken($id, $type, $token, $exp);
                return ['id' => $id, 'type' => $type, 'params' => $payload];
            } else {
                throw new JwtException('token已过期，请重新登录',10008);
            }
        } catch (JwtException $e) {
            throw new JwtException($e->getMessage(),$e->getCode());
        } catch (\Throwable $e) {
            throw new JwtException($e->getMessage(),$e->getCode());
        }
    }

    /**
     * 解密 jwt_token
     * @param string $token
     * @return object
     * @throws JwtException
     */
    public static function decodeToken(string $token)
    {
        $token_arr = explode('.', $token);
        if (count($token_arr) != 3) throw new JwtException('token 无效',10007);

        $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($token_arr[1]));
        if(empty($payload)) throw new JwtException('token解密失败',10007);

        return $payload;
    }

    /**
     * 清理 jwt_token
     * @param int $id
     * @param string $type
     * @param string|null $token
     * @return bool
     */
    public static function clearToken(int $id, string $type, ?string $token = '')
    {
        $cache_key = $type . "_token_" . $id;

        if (!empty($token)) {
            $cache_token = CacheService::get($cache_key);
            $time = time();
            if (!empty($cache_token)) {
                foreach($cache_token as $key => $item) {
                    // 删除登录token
                    if($item[0] == $token) unset($cache_token[$key]);
                    // 删除过期token
                    if($item[1] <= $time) unset($cache_token[$key]);
                }
                CacheService::set($cache_key, $cache_token);
            }
        } else {
            CacheService::set($cache_key, []);
        }
        return true;
    }

    /**
     * jwt_token 过期检验
     * @param int $id
     * @param string $type
     * @param string $token
     * @return bool|int 返回过期时间
     */
    public static function expireTokenCheck(int $id, string $type, string $token)
    {
        $cache_key = $type . "_token_" . $id;
        $cache_token = CacheService::get($cache_key);
        $time = time();
        $expire_time = 0;
        if (!empty($cache_token)) {
            foreach($cache_token as $key => $item) {
                if($item[0] == $token) {
                    //检验token是否过期
                    if($item[1] <= $time) {
                        return false;
                    } else {
                        $expire_time = $item[1];
                    }
                }
            }
        } else {
            return false;
        }
        return $expire_time;
    }

    /**
     * 续期 jwt_token
     * @param int $id
     * @param string $type
     * @param string $token
     * @param int $exp
     * @return int 返回过期时间
     */
    public static function extensionToken(int $id, string $type, string $token, int $exp)
    {
        $cache_key = $type . "_token_" . $id;
        $cache_token = CacheService::get($cache_key);
        $time = time();
        $expire_time = $time + $exp + 60;
        if (!empty($cache_token)) {
            foreach($cache_token as $key => $item) {
                if($item[0] == $token) {
                    $cache_token[$key][1] = $expire_time;
                }
            }
            CacheService::set($cache_key, $cache_token);
        } else {
            CacheService::set($cache_key, ['id' => $id, 'type' => $type, 'expire' => $expire_time]);
        }
        return $expire_time;
    }
}